(* *****************************************************************************
  *                                 PasJSON                                    *
  ******************************************************************************
  *                          Version 2020-03-04-02-20                          *
  ******************************************************************************
  *                                zlib license                                *
  *============================================================================*
  *                                                                            *
  * Copyright (C) 2016-2020, Benjamin Rosseaux (benjamin@rosseaux.de)          *
  *                                                                            *
  * This software is provided 'as-is', without any express or implied          *
  * warranty. In no event will the authors be held liable for any damages      *
  * arising from the use of this software.                                     *
  *                                                                            *
  * Permission is granted to anyone to use this software for any purpose,      *
  * including commercial applications, and to alter it and redistribute it     *
  * freely, subject to the following restrictions:                             *
  *                                                                            *
  * 1. The origin of this software must not be misrepresented; you must not    *
  *    claim that you wrote the original software. If you use this software    *
  *    in a product, an acknowledgement in the product documentation would be  *
  *    appreciated but is not required.                                        *
  * 2. Altered source versions must be plainly marked as such, and must not be *
  *    misrepresented as being the original software.                          *
  * 3. This notice may not be removed or altered from any source distribution. *
  *                                                                            *
  ******************************************************************************
  *                  General guidelines for code contributors                  *
  *============================================================================*
  *                                                                            *
  * 1. Make sure you are legally allowed to make a contribution under the zlib *
  *    license.                                                                *
  * 2. The zlib license header goes at the top of each source file, with       *
  *    appropriate copyright notice.                                           *
  * 3. After a pull request, check the status of your pull request on          *
  *    http://github.com/BeRo1985/pasjson                                      *
  * 4. Write code which's compatible with newer modern Delphi versions and     *
  *    FreePascal >= 3.0.0                                                     *
  * 5. Don't use Delphi-only, FreePascal-only or Lazarus-only libraries/units, *
  *    but if needed, make it out-ifdef-able.                                  *
  * 6. No use of third-party libraries/units as possible, but if needed, make  *
  *    it out-ifdef-able.                                                      *
  * 7. Try to use const when possible.                                         *
  * 8. Make sure to comment out writeln, used while debugging.                 *
  * 9. Make sure the code compiles on 32-bit and 64-bit platforms (x86-32,     *
  *    x86-64, ARM, ARM64, etc.).                                              *
  *                                                                            *
  ***************************************************************************** *)

unit PasJSON;

{$ifdef fpc}
{$mode delphi}
{$ifdef cpui386}
{$define cpu386}
{$endif}
{$ifdef cpu386}
{$asmmode intel}
{$endif}
{$ifdef cpuamd64}
{$asmmode intel}
{$endif}
{$ifdef FPC_LITTLE_ENDIAN}
{$define LITTLE_ENDIAN}
{$else}
{$ifdef FPC_BIG_ENDIAN}
{$define BIG_ENDIAN}
{$endif}
{$endif}
{ -$pic off }
{$ifdef fpc_has_internal_sar}
{$define HasSAR}
{$endif}
{$ifdef FPC_HAS_TYPE_EXTENDED}
{$define HAS_TYPE_EXTENDED}
{$else}
{$undef HAS_TYPE_EXTENDED}
{$endif}
{$ifdef FPC_HAS_TYPE_DOUBLE}
{$define HAS_TYPE_DOUBLE}
{$else}
{$undef HAS_TYPE_DOUBLE}
{$endif}
{$ifdef FPC_HAS_TYPE_SINGLE}
{$define HAS_TYPE_SINGLE}
{$else}
{$undef HAS_TYPE_SINGLE}
{$endif}
{$define CAN_INLINE}
{$define HAS_ADVANCED_RECORDS}
{$else}
{$realcompatibility off}
{$localsymbols on}
{$define LITTLE_ENDIAN}
{$ifndef cpu64}
{$define cpu32}
{$endif}
{$define HAS_TYPE_EXTENDED}
{$define HAS_TYPE_DOUBLE}
{$define HAS_TYPE_SINGLE}
{$undef CAN_INLINE}
{$undef HAS_ADVANCED_RECORDS}
{$ifndef BCB}
{$ifdef ver120}
{$define Delphi4or5}
{$endif}
{$ifdef ver130}
{$define Delphi4or5}
{$endif}
{$ifdef ver140}
{$define Delphi6}
{$endif}
{$ifdef ver150}
{$define Delphi7}
{$endif}
{$ifdef ver170}
{$define Delphi2005}
{$endif}
{$else}
{$ifdef ver120}
{$define Delphi4or5}
{$define BCB4}
{$endif}
{$ifdef ver130}
{$define Delphi4or5}
{$endif}
{$endif}
{$ifdef conditionalexpressions}
{$if CompilerVersion>=24.0}
{$legacyifend on}
{$ifend}
{$if CompilerVersion>=14.0}
{$if CompilerVersion=14.0}
{$define Delphi6}
{$ifend}
{$define Delphi6AndUp}
{$ifend}
{$if CompilerVersion>=15.0}
{$if CompilerVersion=15.0}
{$define Delphi7}
{$ifend}
{$define Delphi7AndUp}
{$ifend}
{$if CompilerVersion>=17.0}
{$if CompilerVersion=17.0}
{$define Delphi2005}
{$ifend}
{$define Delphi2005AndUp}
{$ifend}
{$if CompilerVersion>=18.0}
{$if CompilerVersion=18.0}
{$define BDS2006}
{$define Delphi2006}
{$ifend}
{$define Delphi2006AndUp}
{$define CAN_INLINE}
{$define HAS_ADVANCED_RECORDS}
{$ifend}
{$if CompilerVersion>=18.5}
{$if CompilerVersion=18.5}
{$define Delphi2007}
{$ifend}
{$define Delphi2007AndUp}
{$ifend}
{$if CompilerVersion=19.0}
{$define Delphi2007Net}
{$ifend}
{$if CompilerVersion>=20.0}
{$if CompilerVersion=20.0}
{$define Delphi2009}
{$ifend}
{$define Delphi2009AndUp}
{$ifend}
{$if CompilerVersion>=21.0}
{$if CompilerVersion=21.0}
{$define Delphi2010}
{$ifend}
{$define Delphi2010AndUp}
{$ifend}
{$if CompilerVersion>=22.0}
{$if CompilerVersion=22.0}
{$define DelphiXE}
{$ifend}
{$define DelphiXEAndUp}
{$ifend}
{$if CompilerVersion>=23.0}
{$if CompilerVersion=23.0}
{$define DelphiXE2}
{$ifend}
{$define DelphiXE2AndUp}
{$ifend}
{$if CompilerVersion>=24.0}
{$legacyifend on}
{$if CompilerVersion=24.0}
{$define DelphiXE3}
{$ifend}
{$define DelphiXE3AndUp}
{$ifend}
{$if CompilerVersion>=25.0}
{$if CompilerVersion=25.0}
{$define DelphiXE4}
{$ifend}
{$define DelphiXE4AndUp}
{$ifend}
{$if CompilerVersion>=26.0}
{$if CompilerVersion=26.0}
{$define DelphiXE5}
{$ifend}
{$define DelphiXE5AndUp}
{$ifend}
{$if CompilerVersion>=27.0}
{$if CompilerVersion=27.0}
{$define DelphiXE6}
{$ifend}
{$define DelphiXE6AndUp}
{$ifend}
{$if CompilerVersion>=28.0}
{$if CompilerVersion=28.0}
{$define DelphiXE7}
{$ifend}
{$define DelphiXE7AndUp}
{$ifend}
{$if CompilerVersion>=29.0}
{$if CompilerVersion=29.0}
{$define DelphiXE8}
{$ifend}
{$define DelphiXE8AndUp}
{$ifend}
{$if CompilerVersion>=30.0}
{$if CompilerVersion=30.0}
{$define Delphi10Seattle}
{$ifend}
{$define Delphi10SeattleAndUp}
{$ifend}
{$if CompilerVersion>=31.0}
{$if CompilerVersion=31.0}
{$define Delphi10Berlin}
{$ifend}
{$define Delphi10BerlinAndUp}
{$ifend}
{$endif}
{$ifndef Delphi4or5}
{$ifndef BCB}
{$define Delphi6AndUp}
{$endif}
{$ifndef Delphi6}
{$define BCB6OrDelphi7AndUp}
{$ifndef BCB}
{$define Delphi7AndUp}
{$endif}
{$ifndef BCB}
{$ifndef Delphi7}
{$ifndef Delphi2005}
{$define BDS2006AndUp}
{$endif}
{$endif}
{$endif}
{$endif}
{$endif}
{$ifdef Delphi6AndUp}
{$warn symbol_platform off}
{$warn symbol_deprecated off}
{$endif}
{$endif}
{$if defined(Win32) or defined(Win64)}
{$define Windows}
{$ifend}
{$rangechecks off}
{$extendedsyntax on}
{$writeableconst on}
{$hints off}
{$booleval off}
{$typedaddress off}
{$stackframes off}
{$varstringchecks on}
{$typeinfo on}
{$overflowchecks off}
{$longstrings on}
{$openstrings on}
{$ifndef HAS_TYPE_SINGLE}
{$error No single floating point precision}
{$endif}
{$ifndef HAS_TYPE_DOUBLE}
{$error No double floating point precision}
{$endif}
{$scopedenums on}
{$ifndef fpc}
{$ifdef conditionalexpressions}
{$if CompilerVersion>=24.0}
{$legacyifend on}
{$ifend}
{$endif}
{$endif}

interface

uses
  System.SysUtils,
  System.Classes,
  System.Math;

type
  PPPasJSONInt8 = ^PPasJSONInt8;
  PPasJSONInt8 = ^TPasJSONInt8;
  TPasJSONInt8 = {$IFDEF fpc}Int8{$ELSE}shortint{$ENDIF};

  PPPasJSONUInt8 = ^PPasJSONUInt8;
  PPasJSONUInt8 = ^TPasJSONUInt8;
  TPasJSONUInt8 = {$IFDEF fpc}UInt8{$ELSE}byte{$ENDIF};

  PPPasJSONUInt8Array = ^PPasJSONUInt8Array;
  PPasJSONUInt8Array = ^TPasJSONUInt8Array;
  TPasJSONUInt8Array = array [0 .. 65535] of TPasJSONUInt8;

  PPPasJSONInt16 = ^PPasJSONInt16;
  PPasJSONInt16 = ^TPasJSONInt16;
  TPasJSONInt16 = {$IFDEF fpc}Int16{$ELSE}smallint{$ENDIF};

  PPPasJSONUInt16 = ^PPasJSONUInt16;
  PPasJSONUInt16 = ^TPasJSONUInt16;
  TPasJSONUInt16 = {$IFDEF fpc}UInt16{$ELSE}word{$ENDIF};

  PPPasJSONInt32 = ^PPasJSONInt32;
  PPasJSONInt32 = ^TPasJSONInt32;
  TPasJSONInt32 = {$IFDEF fpc}Int32{$ELSE}longint{$ENDIF};

  PPPasJSONUInt32 = ^PPasJSONUInt32;
  PPasJSONUInt32 = ^TPasJSONUInt32;
  TPasJSONUInt32 = {$IFDEF fpc}UInt32{$ELSE}longword{$ENDIF};

  PPPasJSONInt64 = ^PPasJSONInt64;
  PPasJSONInt64 = ^TPasJSONInt64;
  TPasJSONInt64 = Int64;

  PPPasJSONUInt64 = ^PPasJSONUInt64;
  PPasJSONUInt64 = ^TPasJSONUInt64;
  TPasJSONUInt64 = UInt64;

  PPPasJSONChar = ^PAnsiChar;
  PPasJSONChar = PAnsiChar;
  TPasJSONChar = AnsiChar;

  PPPasJSONRawByteChar = ^PAnsiChar;
  PPasJSONRawByteChar = PAnsiChar;
  TPasJSONRawByteChar = AnsiChar;

  PPPasJSONUTF16Char = ^PWideChar;
  PPasJSONUTF16Char = PWideChar;
  TPasJSONUTF16Char = WideChar;

  PPPasJSONPointer = ^PPasJSONPointer;
  PPasJSONPointer = ^TPasJSONPointer;
  TPasJSONPointer = Pointer;

  PPPasJSONPointers = ^PPasJSONPointers;
  PPasJSONPointers = ^TPasJSONPointers;
  TPasJSONPointers = array [0 .. 65535] of TPasJSONPointer;

  PPPasJSONVoid = ^PPasJSONVoid;
  PPasJSONVoid = TPasJSONPointer;

  PPPasJSONFloat = ^PPasJSONFloat;
  PPasJSONFloat = ^TPasJSONFloat;
  TPasJSONFloat = Single;

  TPasJSONFloats = array of TPasJSONFloat;

  PPPasJSONDouble = ^PPasJSONDouble;
  PPasJSONDouble = ^TPasJSONDouble;
  TPasJSONDouble = Double;

  PPPasJSONPtrUInt = ^PPasJSONPtrUInt;
  PPPasJSONPtrInt = ^PPasJSONPtrInt;
  PPasJSONPtrUInt = ^TPasJSONPtrUInt;
  PPasJSONPtrInt = ^TPasJSONPtrInt;
{$IFDEF fpc}
  TPasJSONPtrUInt = PtrUInt;
  TPasJSONPtrInt = PtrInt;
{$UNDEF OldDelphi}
{$ELSE}
{$IFDEF conditionalexpressions}
{$IF CompilerVersion>=23.0}
{$UNDEF OldDelphi}
  TPasJSONPtrUInt = NativeUInt;
  TPasJSONPtrInt = NativeInt;
{$ELSE}
{$DEFINE OldDelphi}
{$IFEND}
{$ELSE}
{$DEFINE OldDelphi}
{$ENDIF}
{$ENDIF}
{$IFDEF OldDelphi}
{$IFDEF cpu64}
  TPasJSONPtrUInt = UInt64;
  TPasJSONPtrInt = Int64;
{$ELSE}
  TPasJSONPtrUInt = longword;
  TPasJSONPtrInt = longint;
{$ENDIF}
{$ENDIF}
  PPPasJSONSizeUInt = ^PPasJSONSizeUInt;
  PPasJSONSizeUInt = ^TPasJSONSizeUInt;
  TPasJSONSizeUInt = TPasJSONPtrUInt;

  PPPasJSONSizeInt = ^PPasJSONSizeInt;
  PPasJSONSizeInt = ^TPasJSONSizeInt;
  TPasJSONSizeInt = TPasJSONPtrInt;

  PPPasJSONNativeUInt = ^PPasJSONNativeUInt;
  PPasJSONNativeUInt = ^TPasJSONNativeUInt;
  TPasJSONNativeUInt = TPasJSONPtrUInt;

  PPPasJSONNativeInt = ^PPasJSONNativeInt;
  PPasJSONNativeInt = ^TPasJSONNativeInt;
  TPasJSONNativeInt = TPasJSONPtrInt;

  PPPasJSONSize = ^PPasJSONSizeUInt;
  PPasJSONSize = ^TPasJSONSizeUInt;
  TPasJSONSize = TPasJSONPtrUInt;

  PPPasJSONPtrDiff = ^PPasJSONPtrDiff;
  PPasJSONPtrDiff = ^TPasJSONPtrDiff;
  TPasJSONPtrDiff = TPasJSONPtrInt;

  PPPasJSONRawByteString = ^PPasJSONRawByteString;
  PPasJSONRawByteString = ^TPasJSONRawByteString;
  TPasJSONRawByteString =
  {$IF declared(RawByteString)}RawByteString{$ELSE}AnsiString{$IFEND};

  PPPasJSONUTF8String = ^PPasJSONUTF8String;
  PPasJSONUTF8String = ^TPasJSONUTF8String;
  TPasJSONUTF8String =
  {$IF declared(UTF8String)}UTF8String{$ELSE}AnsiString{$IFEND};

  PPPasJSONUTF16String = ^PPasJSONUTF16String;
  PPasJSONUTF16String = ^TPasJSONUTF16String;
  TPasJSONUTF16String =
  {$IF declared(UnicodeString)}UnicodeString{$ELSE}WideString{$IFEND};

  EPasJSONSyntaxError = class(Exception)
  private
    fPosition: TPasJSONSizeInt;
  public
    constructor Create(const aMessage: string;
      const aPosition: TPasJSONSizeInt); reintroduce;
  published
    property Position: TPasJSONSizeInt read fPosition write fPosition;
  end;

  EPasJSONMergeError = class(Exception);

  TPasJSONMergeFlag = (ForceObjectPropertyValueDestinationType);

  TPasJSONMergeFlags = set of TPasJSONMergeFlag;

  TPasJSONItem = class
  public
    constructor Create;
    destructor Destroy; override;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); virtual;
  end;

  TPasJSONItems = array of TPasJSONItem;

  TPasJSONItemNull = class(TPasJSONItem)
  public
    constructor Create;
    destructor Destroy; override;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
  end;

  TPasJSONItemBoolean = class(TPasJSONItem)
  private
    fValue: boolean;
  public
    constructor Create(const AValue: boolean);
    destructor Destroy; override;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
  published
    property Value: boolean read fValue write fValue;
  end;

  TPasJSONItemNumber = class(TPasJSONItem)
  private
    fValue: TPasJSONDouble;
  public
    constructor Create(const AValue: TPasJSONDouble);
    destructor Destroy; override;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
  published
    property Value: TPasJSONDouble read fValue write fValue;
  end;

  TPasJSONItemString = class(TPasJSONItem)
  private
    fValue: TPasJSONUTF8String;
  public
    constructor Create(const AValue: TPasJSONUTF8String);
    destructor Destroy; override;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
  published
    property Value: TPasJSONUTF8String read fValue write fValue;
  end;

  TPasJSONItemObjectProperty = class
  private
    fKey: TPasJSONUTF8String;
    fValue: TPasJSONItem;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
  published
    property Key: TPasJSONUTF8String read fKey write fKey;
    property Value: TPasJSONItem read fValue write fValue;
  end;

  TPasJSONItemObjectProperties = array of TPasJSONItemObjectProperty;

  TPasJSONItemObject = class(TPasJSONItem)
  public type
    PPasJSONItemObjectEnumerator = ^TPasJSONItemObjectEnumerator;

    TPasJSONItemObjectEnumerator = record
    private
      fOwner: TPasJSONItemObject;
      fIndex: TPasJSONSizeInt;
      function GetCurrent: TPasJSONItemObjectProperty; inline;
    public
      constructor Create(const aOwner: TPasJSONItemObject);
      function MoveNext: boolean; inline;
      property Current: TPasJSONItemObjectProperty read GetCurrent;
    end;
  private
    fProperties: TPasJSONItemObjectProperties;
    fCount: TPasJSONSizeInt;
    function GetKeyIndex(const aKey: TPasJSONUTF8String): TPasJSONInt32;
    function GetKey(const aIndex: TPasJSONSizeInt): TPasJSONUTF8String;
    procedure SetKey(const aIndex: TPasJSONSizeInt;
      const aKey: TPasJSONUTF8String);
    function GetValue(const aIndex: TPasJSONSizeInt): TPasJSONItem;
    procedure SetValue(const aIndex: TPasJSONSizeInt;
      const aItem: TPasJSONItem);
    function GetProperty(const aKey: TPasJSONUTF8String): TPasJSONItem;
    procedure SetProperty(const aKey: TPasJSONUTF8String;
      const aItem: TPasJSONItem);
  public
    constructor Create;
    destructor Destroy; override;
    function GetEnumerator: TPasJSONItemObjectEnumerator; inline;
    procedure Clear;
    procedure Add(const aKey: TPasJSONUTF8String; const AValue: TPasJSONItem);
    procedure Delete(const aIndex: TPasJSONSizeInt); overload;
    procedure Delete(const aKey: TPasJSONUTF8String); overload;
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
    property Count: TPasJSONSizeInt read fCount;
    property Indices[const Key: TPasJSONUTF8String]: TPasJSONInt32
      read GetKeyIndex;
    property Keys[const Index: TPasJSONSizeInt]: TPasJSONUTF8String read GetKey
      write SetKey;
    property Values[const Index: TPasJSONSizeInt]: TPasJSONItem read GetValue
      write SetValue;
    property Properties[const Key: TPasJSONUTF8String]: TPasJSONItem
      read GetProperty write SetProperty; default;
  end;

  TPasJSONItemArray = class(TPasJSONItem)
  public type
    PPasJSONItemArrayEnumerator = ^TPasJSONItemArrayEnumerator;

    TPasJSONItemArrayEnumerator = record
    private
      fOwner: TPasJSONItemArray;
      fIndex: TPasJSONSizeInt;
      function GetCurrent: TPasJSONItem; inline;
    public
      constructor Create(const aOwner: TPasJSONItemArray);
      function MoveNext: boolean; inline;
      property Current: TPasJSONItem read GetCurrent;
    end;
  private
    fItems: TPasJSONItems;
    fCount: TPasJSONInt32;
    function GetValue(const Index: TPasJSONInt32): TPasJSONItem;
    procedure SetValue(const Index: TPasJSONInt32; const Item: TPasJSONItem);
  public
    constructor Create;
    destructor Destroy; override;
    function GetEnumerator: TPasJSONItemArrayEnumerator; inline;
    procedure Clear;
    procedure Add(const AValue: TPasJSONItem);
    procedure Delete(const aIndex: TPasJSONSizeInt);
    procedure Merge(const aWith: TPasJSONItem;
      const aFlags: TPasJSONMergeFlags = []); override;
    property Count: TPasJSONInt32 read fCount;
    property Items[const Index: TPasJSONInt32]: TPasJSONItem read GetValue
      write SetValue; default;
  end;

  PPasJSONModeFlag = ^TPasJSONModeFlag;
  TPasJSONModeFlag = (UnquotedKeys, Comments, ImplicitRootObject,
    OptionalCommas, EqualsForColon, MultilineStrings, HexadecimalNumbers);

  PPasJSONModeFlags = ^TPasJSONModeFlag;
  TPasJSONModeFlags = set of TPasJSONModeFlag;

  PPasJSONEncoding = ^TPasJSONEncoding;
  TPasJSONEncoding = (AutomaticDetection, Latin1, UTF8, UTF16LE, UTF16BE,
    UTF32LE, UTF32BE);

  TPasJSON = class
  public
  // Simplified JSON notation as in http://bitsquid.blogspot.de/2009/10/simplified-json-notation.html
    const
    SimplifiedJSONModeFlags: TPasJSONModeFlags = [TPasJSONModeFlag.UnquotedKeys,
      TPasJSONModeFlag.Comments, TPasJSONModeFlag.ImplicitRootObject,
      TPasJSONModeFlag.OptionalCommas, TPasJSONModeFlag.EqualsForColon,
      TPasJSONModeFlag.MultilineStrings];
  public
    class function StringQuote(const aString: TPasJSONUTF8String)
      : TPasJSONRawByteString; static;
    class function Parse(const aSource: TPasJSONRawByteString;
      const aModeFlags: TPasJSONModeFlags = [TPasJSONModeFlag.Comments];
      const aEncoding: TPasJSONEncoding = TPasJSONEncoding.AutomaticDetection)
      : TPasJSONItem; overload; static;
    class function Parse(const aStream: TStream;
      const aModeFlags: TPasJSONModeFlags = [TPasJSONModeFlag.Comments];
      const aEncoding: TPasJSONEncoding = TPasJSONEncoding.AutomaticDetection)
      : TPasJSONItem; overload; static;
    class function Stringify(const aJSONItem: TPasJSONItem;
      const aFormatting: boolean = false;
      const aModeFlags: TPasJSONModeFlags = []; const aLevel: TPasJSONInt32 = 0)
      : TPasJSONRawByteString; static;
    class procedure StringifyToStream(const aStream: TStream;
      const aJSONItem: TPasJSONItem; const aFormatting: boolean = false;
      const aModeFlags: TPasJSONModeFlags = [];
      const aLevel: TPasJSONInt32 = 0); static;
    class function GetNumber(const aItem: TPasJSONItem;
      const aDefault: TPasJSONDouble = 0.0): TPasJSONDouble; static;
    class function GetInt64(const aItem: TPasJSONItem;
      const aDefault: TPasJSONInt64 = 0): TPasJSONInt64; static;
    class function GetString(const aItem: TPasJSONItem;
      const aDefault: TPasJSONUTF8String = ''): TPasJSONUTF8String; static;
    class function GetBoolean(const aItem: TPasJSONItem;
      const aDefault: boolean = false): boolean; static;
    class function LoadBinaryFromStream(const aStream: TStream)
      : TPasJSONItem; static;
    class procedure SaveBinaryToStream(const aStream: TStream;
      const aJSONItem: TPasJSONItem); static;
  end;

implementation

uses
  PasDblStrUtils;

// 0 1 2 3 4 5 6 7 8 9 a b c d e f
const { UTF8CharSteps:array[AnsiChar] of byte=(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 0
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 1
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 2
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 3
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 4
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 5
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 6
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 7
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 8
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // 9
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // a
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // b
    1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  // c
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  // d
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,  // e
    4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1); // f
    //0 1 2 3 4 5 6 7 8 9 a b c d e f }

  UTF8DFACharClasses: array [AnsiChar] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
    3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8);

  UTF8DFATransitions: array [byte] of byte = (0, 16, 32, 48, 80, 128, 112, 16,
    16, 16, 64, 96, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 0, 16, 16, 16, 16, 16,
    16, 16, 32, 16, 16, 16, 16, 16, 32, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16,
    16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 48,
    16, 48, 16, 16, 16, 16, 16, 16, 16, 48, 16, 16, 16, 16, 16, 48, 16, 48, 16,
    16, 16, 16, 16, 16, 16, 48, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16);

  suDONOTKNOW = -1;
  suNOUTF8 = 0;
  suPOSSIBLEUTF8 = 1;
  suISUTF8 = 2;

  ucACCEPT = 0;
  ucERROR = 16;

function GetNextUTF8Char(const aString: PPasJSONRawByteChar;
  const aStringLength: TPasJSONInt32; var aCodeUnit: TPasJSONInt32)
  : TPasJSONUInt32;
var
  StartCodeUnit, Value, CharClass, State: TPasJSONUInt32;
begin
  result := 0;
  if (aCodeUnit > 0) and (aCodeUnit <= aStringLength) then
  begin
    dec(aCodeUnit);
    StartCodeUnit := aCodeUnit;
    State := ucACCEPT;
    while aCodeUnit < aStringLength do
    begin
      Value := byte(AnsiChar(aString[aCodeUnit]));
      inc(aCodeUnit);
      CharClass := UTF8DFACharClasses[AnsiChar(Value)];
      if State = ucACCEPT then
      begin
        result := Value and ($FF shr CharClass);
      end
      else
      begin
        result := (result shl 6) or (Value and $3F);
      end;
      State := UTF8DFATransitions[State + CharClass];
      if State <= ucERROR then
      begin
        break;
      end;
    end;
    if State <> ucACCEPT then
    begin
      result := byte(AnsiChar(aString[StartCodeUnit]));
      aCodeUnit := StartCodeUnit + 1;
    end;
    inc(aCodeUnit);
  end;
end;

function ConvertUTF16ToUTF8(const aUTF16String: TPasJSONUTF16String)
  : TPasJSONUTF8String;
var
  i, j: TPasJSONInt32;
  w: UInt16;
  u4c: TPasJSONUInt32;
begin
  result := '';
  j := 0;
  i := 1;
  while i <= length(aUTF16String) do
  begin
    w := UInt16(TPasJSONUTF16Char(aUTF16String[i]));
    if (w <= $D7FF) or (w >= $E000) then
    begin
      u4c := w;
      inc(i);
    end
    else if ((i + 1) <= length(aUTF16String)) and ((w >= $D800) and (w <= $DBFF)
      ) and ((UInt16(aUTF16String[i + 1]) >= $DC00) and
      (UInt16(aUTF16String[i + 1]) <= $DFFF)) then
    begin
      u4c := (TPasJSONUInt32(TPasJSONUInt32(w and $3FF) shl 10) or
        TPasJSONUInt32(UInt16(TPasJSONUTF16Char(aUTF16String[i + 1])) and $3FF)
        ) + $10000;
      inc(i, 2);
    end
    else
    begin
      u4c := $FFFD;
      inc(i);
    end;
    if u4c <= $7F then
    begin
      inc(j);
    end
    else if u4c <= $7FF then
    begin
      inc(j, 2);
    end
    else if u4c <= $FFFF then
    begin
      inc(j, 3);
    end
    else if u4c <= $1FFFFF then
    begin
      inc(j, 4);
    end
    else
    begin
      inc(j, 3);
    end;
  end;
  SetLength(result, j);
  j := 1;
  i := 1;
  while i <= length(aUTF16String) do
  begin
    w := UInt16(TPasJSONUTF16Char(aUTF16String[i]));
    if (w <= $D7FF) or (w >= $E000) then
    begin
      u4c := w;
      inc(i);
    end
    else if ((i + 1) <= length(aUTF16String)) and ((w >= $D800) and (w <= $DBFF)
      ) and ((UInt16(aUTF16String[i + 1]) >= $DC00) and
      (UInt16(aUTF16String[i + 1]) <= $DFFF)) then
    begin
      u4c := (TPasJSONUInt32(TPasJSONUInt32(w and $3FF) shl 10) or
        TPasJSONUInt32(UInt16(TPasJSONUTF16Char(aUTF16String[i + 1])) and $3FF)
        ) + $10000;
      inc(i, 2);
    end
    else
    begin
      u4c := $FFFD;
      inc(i);
    end;
    if u4c <= $7F then
    begin
      result[j] := AnsiChar(byte(u4c));
      inc(j);
    end
    else if u4c <= $7FF then
    begin
      result[j] := AnsiChar(byte($C0 or ((u4c shr 6) and $1F)));
      result[j + 1] := AnsiChar(byte($80 or (u4c and $3F)));
      inc(j, 2);
    end
    else if u4c <= $FFFF then
    begin
      result[j] := AnsiChar(byte($E0 or ((u4c shr 12) and $0F)));
      result[j + 1] := AnsiChar(byte($80 or ((u4c shr 6) and $3F)));
      result[j + 2] := AnsiChar(byte($80 or (u4c and $3F)));
      inc(j, 3);
    end
    else if u4c <= $1FFFFF then
    begin
      result[j] := AnsiChar(byte($F0 or ((u4c shr 18) and $07)));
      result[j + 1] := AnsiChar(byte($80 or ((u4c shr 12) and $3F)));
      result[j + 2] := AnsiChar(byte($80 or ((u4c shr 6) and $3F)));
      result[j + 3] := AnsiChar(byte($80 or (u4c and $3F)));
      inc(j, 4);
    end
    else
    begin
      u4c := $FFFD;
      result[j] := AnsiChar(byte($E0 or (u4c shr 12)));
      result[j + 1] := AnsiChar(byte($80 or ((u4c shr 6) and $3F)));
      result[j + 2] := AnsiChar(byte($80 or (u4c and $3F)));
      inc(j, 3);
    end;
  end;
end;

constructor EPasJSONSyntaxError.Create(const aMessage: string;
  const aPosition: TPasJSONSizeInt);
begin
  inherited Create(aMessage);
  fPosition := aPosition;
end;

constructor TPasJSONItem.Create;
begin
  inherited Create;
end;

destructor TPasJSONItem.Destroy;
begin
  inherited Destroy;
end;

procedure TPasJSONItem.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
begin
  if not(assigned(aWith) and (aWith is TPasJSONItem)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
end;

constructor TPasJSONItemNull.Create;
begin
  inherited Create;
end;

destructor TPasJSONItemNull.Destroy;
begin
  inherited Destroy;
end;

procedure TPasJSONItemNull.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemNull)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
end;

constructor TPasJSONItemBoolean.Create(const AValue: boolean);
begin
  inherited Create;
  fValue := AValue;
end;

destructor TPasJSONItemBoolean.Destroy;
begin
  inherited Destroy;
end;

procedure TPasJSONItemBoolean.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemBoolean)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
  fValue := TPasJSONItemBoolean(aWith).Value;
end;

constructor TPasJSONItemNumber.Create(const AValue: TPasJSONDouble);
begin
  inherited Create;
  fValue := AValue;
end;

destructor TPasJSONItemNumber.Destroy;
begin
  inherited Destroy;
end;

procedure TPasJSONItemNumber.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemNumber)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
  fValue := TPasJSONItemNumber(aWith).Value;
end;

constructor TPasJSONItemString.Create(const AValue: TPasJSONUTF8String);
begin
  inherited Create;
  fValue := AValue;
end;

destructor TPasJSONItemString.Destroy;
begin
  fValue := '';
  inherited Destroy;
end;

procedure TPasJSONItemString.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemString)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
  fValue := TPasJSONItemString(aWith).Value;
end;

constructor TPasJSONItemObjectProperty.Create;
begin
  inherited Create;
  fKey := '';
  fValue := nil;
end;

destructor TPasJSONItemObjectProperty.Destroy;
begin
  fKey := '';
  FreeAndNil(fValue);
  inherited Destroy;
end;

constructor TPasJSONItemObject.TPasJSONItemObjectEnumerator.Create
  (const aOwner: TPasJSONItemObject);
begin
  fOwner := aOwner;
  fIndex := -1;
end;

function TPasJSONItemObject.TPasJSONItemObjectEnumerator.MoveNext: boolean;
begin
  inc(fIndex);
  result := (fIndex >= 0) and (fIndex < fOwner.fCount);
end;

function TPasJSONItemObject.TPasJSONItemObjectEnumerator.GetCurrent
  : TPasJSONItemObjectProperty;
begin
  result := fOwner.fProperties[fIndex];
end;

constructor TPasJSONItemObject.Create;
begin
  inherited Create;
  fProperties := nil;
  fCount := 0;
end;

destructor TPasJSONItemObject.Destroy;
var
  Index: TPasJSONInt32;
begin
  for Index := 0 to fCount - 1 do
  begin
    FreeAndNil(fProperties[Index]);
  end;
  SetLength(fProperties, 0);
  inherited Destroy;
end;

function TPasJSONItemObject.GetKeyIndex(const aKey: TPasJSONUTF8String)
  : TPasJSONInt32;
var
  Index: TPasJSONInt32;
begin
  for Index := 0 to fCount - 1 do
  begin
    if fProperties[Index].Key = aKey then
    begin
      result := Index;
      exit;
    end;
  end;
  result := -1;
end;

function TPasJSONItemObject.GetKey(const aIndex: TPasJSONSizeInt)
  : TPasJSONUTF8String;
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    result := fProperties[aIndex].Key;
  end
  else
  begin
    result := '';
  end;
end;

procedure TPasJSONItemObject.SetKey(const aIndex: TPasJSONSizeInt;
  const aKey: TPasJSONUTF8String);
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    fProperties[aIndex].Key := aKey;
  end;
end;

function TPasJSONItemObject.GetValue(const aIndex: TPasJSONSizeInt)
  : TPasJSONItem;
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    result := fProperties[aIndex].Value;
  end
  else
  begin
    result := nil;
  end;
end;

procedure TPasJSONItemObject.SetValue(const aIndex: TPasJSONSizeInt;
  const aItem: TPasJSONItem);
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    fProperties[aIndex].Value := aItem;
  end;
end;

function TPasJSONItemObject.GetProperty(const aKey: TPasJSONUTF8String)
  : TPasJSONItem;
begin
  result := GetValue(GetKeyIndex(aKey));
end;

procedure TPasJSONItemObject.SetProperty(const aKey: TPasJSONUTF8String;
  const aItem: TPasJSONItem);
begin
  SetValue(GetKeyIndex(aKey), aItem);
end;

function TPasJSONItemObject.GetEnumerator
  : TPasJSONItemObject.TPasJSONItemObjectEnumerator;
begin
  result := TPasJSONItemObject.TPasJSONItemObjectEnumerator.Create(self);
end;

procedure TPasJSONItemObject.Clear;
var
  Index: TPasJSONInt32;
begin
  for Index := 0 to fCount - 1 do
  begin
    FreeAndNil(fProperties[Index]);
  end;
  SetLength(fProperties, 0);
  fCount := 0;
end;

procedure TPasJSONItemObject.Add(const aKey: TPasJSONUTF8String;
  const AValue: TPasJSONItem);
var
  Index: TPasJSONSizeInt;
  ObjectProperty: TPasJSONItemObjectProperty;
begin
  Index := fCount;
  inc(fCount);
  if fCount >= length(fProperties) then
  begin
    SetLength(fProperties, fCount * 2);
  end;
  ObjectProperty := TPasJSONItemObjectProperty.Create;
  fProperties[Index] := ObjectProperty;
  ObjectProperty.Key := aKey;
  ObjectProperty.Value := AValue;
end;

procedure TPasJSONItemObject.Delete(const aIndex: TPasJSONSizeInt);
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    FreeAndNil(fProperties[aIndex]);
    dec(fCount);
    Move(fProperties[aIndex + 1], fProperties[aIndex],
      fCount * SizeOf(TPasJSONItemObjectProperty));
  end;
end;

procedure TPasJSONItemObject.Delete(const aKey: TPasJSONUTF8String);
begin
  Delete(GetKeyIndex(aKey));
end;

procedure TPasJSONItemObject.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
var
  Index, KeyIndex: TPasJSONSizeInt;
  SrcProperty, OurProperty: TPasJSONItemObjectProperty;
  NewItem: TPasJSONItem;
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemObject)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
  for Index := 0 to TPasJSONItemObject(aWith).Count - 1 do
  begin
    SrcProperty := TPasJSONItemObject(aWith).fProperties[Index];
    if assigned(SrcProperty.Value) then
    begin
      KeyIndex := GetKeyIndex(SrcProperty.Key);
      if KeyIndex >= 0 then
      begin
        OurProperty := fProperties[KeyIndex];
        if (TPasJSONMergeFlag.ForceObjectPropertyValueDestinationType in aFlags)
          and ((not assigned(OurProperty.Value)) or
          (assigned(OurProperty.Value) and (OurProperty.Value.ClassType <>
          SrcProperty.Value.ClassType))) then
        begin
          if assigned(OurProperty.Value) then
          begin
            OurProperty.Value.Free;
          end;
          OurProperty.Value := TPasJSONItem(SrcProperty.Value.ClassType.Create);
        end;
        if assigned(OurProperty.Value) and
          (OurProperty.Value.ClassType = SrcProperty.Value.ClassType) then
        begin
          OurProperty.Value.Merge(SrcProperty.Value, aFlags);
        end;
      end
      else
      begin
        NewItem := nil;
        try
          NewItem := TPasJSONItem(SrcProperty.Value.ClassType.Create);
          NewItem.Merge(SrcProperty.Value, aFlags);
          Add(SrcProperty.Key, NewItem);
        except
          NewItem.Free;
          raise;
        end;
      end;
    end;
  end;
end;

constructor TPasJSONItemArray.TPasJSONItemArrayEnumerator.Create
  (const aOwner: TPasJSONItemArray);
begin
  fOwner := aOwner;
  fIndex := -1;
end;

function TPasJSONItemArray.TPasJSONItemArrayEnumerator.MoveNext: boolean;
begin
  inc(fIndex);
  result := (fIndex >= 0) and (fIndex < fOwner.fCount);
end;

function TPasJSONItemArray.TPasJSONItemArrayEnumerator.GetCurrent: TPasJSONItem;
begin
  result := fOwner.fItems[fIndex];
end;

constructor TPasJSONItemArray.Create;
begin
  inherited Create;
  fItems := nil;
  fCount := 0;
end;

destructor TPasJSONItemArray.Destroy;
var
  Index: TPasJSONInt32;
begin
  for Index := 0 to fCount - 1 do
  begin
    FreeAndNil(fItems[Index]);
  end;
  SetLength(fItems, 0);
  inherited Destroy;
end;

function TPasJSONItemArray.GetValue(const Index: TPasJSONInt32): TPasJSONItem;
begin
  if (Index >= 0) and (Index < fCount) then
  begin
    result := fItems[Index];
  end
  else
  begin
    result := nil;
  end;
end;

procedure TPasJSONItemArray.SetValue(const Index: TPasJSONInt32;
  const Item: TPasJSONItem);
begin
  if (Index >= 0) and (Index < fCount) then
  begin
    fItems[Index] := Item;
  end;
end;

function TPasJSONItemArray.GetEnumerator
  : TPasJSONItemArray.TPasJSONItemArrayEnumerator;
begin
  result := TPasJSONItemArray.TPasJSONItemArrayEnumerator.Create(self);
end;

procedure TPasJSONItemArray.Clear;
var
  Index: TPasJSONInt32;
begin
  for Index := 0 to fCount - 1 do
  begin
    FreeAndNil(fItems[Index]);
  end;
  SetLength(fItems, 0);
  fCount := 0;
end;

procedure TPasJSONItemArray.Add(const AValue: TPasJSONItem);
var
  Index: TPasJSONSizeInt;
begin
  Index := fCount;
  inc(fCount);
  if fCount >= length(fItems) then
  begin
    SetLength(fItems, fCount * 2);
  end;
  fItems[Index] := AValue;
end;

procedure TPasJSONItemArray.Delete(const aIndex: TPasJSONSizeInt);
begin
  if (aIndex >= 0) and (aIndex < fCount) then
  begin
    FreeAndNil(fItems[aIndex]);
    dec(fCount);
    Move(fItems[aIndex + 1], fItems[aIndex], fCount * SizeOf(TPasJSONItem));
  end;
end;

procedure TPasJSONItemArray.Merge(const aWith: TPasJSONItem;
  const aFlags: TPasJSONMergeFlags = []);
var
  Index: TPasJSONSizeInt;
  Item, NewItem: TPasJSONItem;
begin
  if not(assigned(aWith) and (aWith is TPasJSONItemArray)) then
  begin
    raise EPasJSONMergeError.Create('Incompatible data type');
  end;
  for Index := 0 to TPasJSONItemArray(aWith).Count - 1 do
  begin
    Item := TPasJSONItemArray(aWith).Items[Index];
    if assigned(Item) then
    begin
      NewItem := TPasJSONItem(Item.ClassType.Create);
      Add(NewItem);
      NewItem.Merge(Item, aFlags);
    end;
  end;
end;

const
  HexChars: array [boolean, 0 .. 15] of AnsiChar = (('0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'),
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
    'E', 'F'));

class function TPasJSON.StringQuote(const aString: TPasJSONUTF8String)
  : TPasJSONRawByteString;
var
  i, l: TPasJSONInt32;
  c, t: TPasJSONUInt32;
begin
  result := '"';
  i := 1;
  l := length(aString);
  while i <= l do
  begin
    case aString[i] of
      '"', '\':
        begin
          result := result + '\' + AnsiChar(aString[i]);
          inc(i);
        end;
      #$08:
        begin
          result := result + '\b';
          inc(i);
        end;
      #$09:
        begin
          result := result + '\t';
          inc(i);
        end;
      #$0a:
        begin
          result := result + '\n';
          inc(i);
        end;
      #$0b:
        begin
          result := result + '\v';
          inc(i);
        end;
      #$0c:
        begin
          result := result + '\f';
          inc(i);
        end;
      #$0d:
        begin
          result := result + '\r';
          inc(i);
        end;
      #$00 .. #$07, #$0e .. #$1f, #$7e .. #$7f:
        begin
          c := byte(AnsiChar(aString[i]));
          result := result + '\u00' + HexChars[false, (c shr 4) and $F] +
            HexChars[false, c and $F];
          inc(i);
        end;
      #$80 .. #$ff:
        begin
          c := GetNextUTF8Char(PAnsiChar(@aString[1]), l, i);
          case c of
            $0000 .. $D7FF, $F000 .. $FFFC:
              begin
                result := result + '\u' + HexChars[false, (c shr 12) and $F] +
                  HexChars[false, (c shr 8) and $F] + HexChars
                  [false, (c shr 4) and $F] + HexChars[false, c and $F];
              end;
            $100000 .. $10FFFF:
              begin
                dec(c, $100000);
                t := (c shr 10) or $D800;
                result := result + '\u' + HexChars[false, (t shr 12) and $F] +
                  HexChars[false, (t shr 8) and $F] + HexChars
                  [false, (t shr 4) and $F] + HexChars[false, t and $F];
                t := (c and $3FF) or $DC00;
                result := result + '\u' + HexChars[false, (t shr 12) and $F] +
                  HexChars[false, (t shr 8) and $F] + HexChars
                  [false, (t shr 4) and $F] + HexChars[false, t and $F];
              end;
          else { -$d800..$dfff,$fffd..$ffff,$110000..$ffffffff: }
            begin
              result := result + '\ufffd';
            end;
          end;
        end;
    else
      begin
        result := result + AnsiChar(aString[i]);
        inc(i);
      end;
    end;
  end;
  result := result + '"';
end;

class function TPasJSON.Parse(const aSource: TPasJSONRawByteString;
  const aModeFlags: TPasJSONModeFlags = [TPasJSONModeFlag.Comments];
  const aEncoding: TPasJSONEncoding = TPasJSONEncoding.AutomaticDetection)
  : TPasJSONItem;
var
  Position: TPasJSONInt32;
  CurrentChar: TPasJSONUInt32;
  CharEOF: boolean;
  Encoding: TPasJSONEncoding;
  procedure NextChar;
  const
    UTF16Shifts: array [TPasJSONEncoding.UTF16LE .. TPasJSONEncoding.UTF16BE,
      0 .. 1] of TPasJSONInt32 = ((0, 8), (8, 0));
  var
    Temp: TPasJSONUInt32;
  begin
    if Position <= length(aSource) then
    begin
      case Encoding of
        TPasJSONEncoding.UTF8:
          begin
            CurrentChar := GetNextUTF8Char(PAnsiChar(@aSource[1]),
              length(aSource), Position);
          end;
        TPasJSONEncoding.UTF16LE, TPasJSONEncoding.UTF16BE:
          begin
            if (Position + 1) <= length(aSource) then
            begin
              CurrentChar := (TPasJSONUInt32(byte(AnsiChar(aSource[Position])))
                shl UTF16Shifts[Encoding, 0]) or
                (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 1])))
                shl UTF16Shifts[Encoding, 1]);
              inc(Position, 2);
              if ((CurrentChar >= $D800) and (CurrentChar <= $DBFF)) and
                ((Position + 1) <= length(aSource)) then
              begin
                Temp := (TPasJSONUInt32(byte(AnsiChar(aSource[Position])))
                  shl UTF16Shifts[Encoding, 0]) or
                  (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 1])))
                  shl UTF16Shifts[Encoding, 1]);
                if (Temp >= $DC00) and (Temp <= $DFFF) then
                begin
                  CurrentChar :=
                    (TPasJSONUInt32(TPasJSONUInt32(CurrentChar and $3FF) shl 10)
                    or TPasJSONUInt32(Temp and $3FF)) + $10000;
                  inc(Position, 2);
                end
                else
                begin
                  CurrentChar := $FFFD;
                end;
              end
              else if not((CurrentChar <= $D7FF) or (CurrentChar >= $E000)) then
              begin
                CurrentChar := $FFFD;
              end;
            end
            else
            begin
              inc(Position);
              CurrentChar := 0;
              CharEOF := true;
            end;
          end;
        TPasJSONEncoding.UTF32LE:
          begin
            if (Position + 3) <= length(aSource) then
            begin
              CurrentChar := (TPasJSONUInt32(byte(AnsiChar(aSource[Position])))
                shl 0) or (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 1])))
                shl 8) or (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 2])))
                shl 16) or
                (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 3]))) shl 24);
              inc(Position, 4);
            end
            else
            begin
              inc(Position);
              CurrentChar := 0;
              CharEOF := true;
            end;
          end;
        TPasJSONEncoding.UTF32BE:
          begin
            if (Position + 3) <= length(aSource) then
            begin
              CurrentChar := (TPasJSONUInt32(byte(AnsiChar(aSource[Position])))
                shl 24) or (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 1]))
                ) shl 16) or
                (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 2]))) shl 8) or
                (TPasJSONUInt32(byte(AnsiChar(aSource[Position + 3]))) shl 0);
              inc(Position, 4);
            end
            else
            begin
              inc(Position);
              CurrentChar := 0;
              CharEOF := true;
            end;
          end;
      else
        begin
          CurrentChar := byte(AnsiChar(aSource[Position]));
          inc(Position);
        end;
      end;
    end
    else
    begin
      CurrentChar := 0;
      CharEOF := true;
    end;
  end;
  procedure JSONError;
  begin
    raise EPasJSONSyntaxError.Create('Parser error at byte position ' +
      IntToStr(Position), Position);
  end;
  procedure SkipWhite;
  var
    LastChar: TPasJSONUInt32;
  begin
    while not CharEOF do
    begin
      case CurrentChar of
        $0009, $000A, $000D, $0020:
          begin
            NextChar;
          end;
        ord('/'):
          begin
            if TPasJSONModeFlag.Comments in aModeFlags then
            begin
              NextChar;
              case CurrentChar of
                ord('/'):
                  begin
                    NextChar;
                    while not(CharEOF or ((CurrentChar = $000A) or
                      (CurrentChar = $000D))) do
                    begin
                      NextChar;
                    end;
                  end;
                ord('*'):
                  begin
                    NextChar;
                    LastChar := 0;
                    while not(CharEOF or ((LastChar = ord('*')) and
                      (CurrentChar = ord('/')))) do
                    begin
                      LastChar := CurrentChar;
                      NextChar;
                    end;
                    if CurrentChar = ord('/') then
                    begin
                      NextChar;
                    end;
                  end;
              else
                begin
                  JSONError;
                end;
              end;
            end
            else
            begin
              JSONError;
            end;
          end;
      else
        begin
          break;
        end;
      end;
    end;
  end;
  function IsChar(c: TPasJSONUTF16Char): boolean;
  begin
    result := (not CharEOF) and (CurrentChar = UInt16(c));
  end;
  procedure ExpectChar(c: TPasJSONUTF16Char);
  begin
    if IsChar(c) then
    begin
      NextChar;
    end
    else
    begin
      JSONError;
    end;
  end;
  function ParseValue(const ImplicitRootObject: boolean): TPasJSONItem;
    function ParseString: TPasJSONItem;
    var
      w: TPasJSONUTF16String;
      // <= because of easy correct handling of UTF16 surrogates (see ECMAScript and JSON specs)
      wl: TPasJSONInt32;
      wc: TPasJSONUInt32;
      procedure AddChar(c: TPasJSONUInt32);
      var
        cl: TPasJSONInt32;
      begin
        if (c >= $100000) and (c <= $10FFFF) then
        begin
          cl := 2;
        end
        else
        begin
          cl := 1;
        end;
        if (wl + cl) > length(w) then
        begin
          SetLength(w, (wl + cl) shl 1);
        end;
        if c <= $D7FF then
        begin
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16(c));
        end
        else if c <= $DFFF then
        begin
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16($FFFD));
        end
        else if c <= $FFFD then
        begin
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16(c));
        end
        else if c <= $FFFF then
        begin
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16($FFFD));
        end
        else if c <= $10FFFF then
        begin
          dec(c, $10000);
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16((c shr 10) or $D800));
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16((c and $3FF) or $DC00));
        end
        else
        begin
          inc(wl);
          w[wl] := TPasJSONUTF16Char(UInt16($FFFD));
        end;
      end;

    begin
      result := nil;
      w := '';
      try
        try
          SetLength(w, 512);
          wl := 0;
          if IsChar('"') then
          begin
            NextChar;
            while not(CharEOF or IsChar('"')) do
            begin
              case CurrentChar of
                $0000 .. $001F:
                  begin
                    if TPasJSONModeFlag.MultilineStrings in aModeFlags then
                    begin
                      AddChar(CurrentChar);
                      NextChar;
                    end
                    else
                    begin
                      JSONError;
                    end;
                  end;
                ord('\'):
                  begin
                    NextChar;
                    case CurrentChar of
                      ord('"'), ord('\'), ord('/'):
                        begin
                          AddChar(CurrentChar);
                          NextChar;
                        end;
                      ord('b'):
                        begin
                          AddChar($0008);
                          NextChar;
                        end;
                      ord('f'):
                        begin
                          AddChar($000C);
                          NextChar;
                        end;
                      ord('n'):
                        begin
                          AddChar($000A);
                          NextChar;
                        end;
                      ord('r'):
                        begin
                          AddChar($000D);
                          NextChar;
                        end;
                      ord('t'):
                        begin
                          AddChar($0009);
                          NextChar;
                        end;
                      ord('u'):
                        begin
                          NextChar;
                          if (((CurrentChar >= ord('0')) and
                            (CurrentChar <= ord('9'))) or
                            ((CurrentChar >= ord('a')) and
                            (CurrentChar <= ord('f'))) or
                            ((CurrentChar >= ord('A')) and
                            (CurrentChar <= ord('F')))) and not CharEOF then
                          begin
                            if (CurrentChar >= ord('0')) and
                              (CurrentChar <= ord('9')) then
                            begin
                              wc := UInt16(CurrentChar) - ord('0');
                            end
                            else if (CurrentChar >= ord('a')) and
                              (CurrentChar <= ord('f')) then
                            begin
                              wc := (UInt16(CurrentChar) - ord('a')) + $A;
                            end
                            else if (CurrentChar >= ord('A')) and
                              (CurrentChar <= ord('F')) then
                            begin
                              wc := (UInt16(CurrentChar) - ord('A')) + $A;
                            end
                            else
                            begin
                              wc := 0;
                            end;
                            wc := wc shl 4;
                            NextChar;
                            if (((CurrentChar >= ord('0')) and
                              (CurrentChar <= ord('9'))) or
                              ((CurrentChar >= ord('a')) and
                              (CurrentChar <= ord('f'))) or
                              ((CurrentChar >= ord('A')) and
                              (CurrentChar <= ord('F')))) and not CharEOF then
                            begin
                              if (CurrentChar >= ord('0')) and
                                (CurrentChar <= ord('9')) then
                              begin
                                wc := wc or
                                  UInt16(UInt16(CurrentChar) - ord('0'));
                              end
                              else if (CurrentChar >= ord('a')) and
                                (CurrentChar <= ord('f')) then
                              begin
                                wc := wc or
                                  UInt16((UInt16(CurrentChar) - ord('a')) + $A);
                              end
                              else if (CurrentChar >= ord('A')) and
                                (CurrentChar <= ord('F')) then
                              begin
                                wc := wc or
                                  UInt16((UInt16(CurrentChar) - ord('A')) + $A);
                              end;
                              wc := wc shl 4;
                              NextChar;
                              if (((CurrentChar >= ord('0')) and
                                (CurrentChar <= ord('9'))) or
                                ((CurrentChar >= ord('a')) and
                                (CurrentChar <= ord('f'))) or
                                ((CurrentChar >= ord('A')) and
                                (CurrentChar <= ord('F')))) and not CharEOF then
                              begin
                                if (CurrentChar >= ord('0')) and
                                  (CurrentChar <= ord('9')) then
                                begin
                                  wc := wc or
                                    UInt16(UInt16(CurrentChar) - ord('0'));
                                end
                                else if (CurrentChar >= ord('a')) and
                                  (CurrentChar <= ord('f')) then
                                begin
                                  wc := wc or
                                    UInt16((UInt16(CurrentChar) -
                                    ord('a')) + $A);
                                end
                                else if (CurrentChar >= ord('A')) and
                                  (CurrentChar <= ord('F')) then
                                begin
                                  wc := wc or
                                    UInt16((UInt16(CurrentChar) -
                                    ord('A')) + $A);
                                end;
                                wc := wc shl 4;
                                NextChar;
                                if (((CurrentChar >= ord('0')) and
                                  (CurrentChar <= ord('9'))) or
                                  ((CurrentChar >= ord('a')) and
                                  (CurrentChar <= ord('f'))) or
                                  ((CurrentChar >= ord('A')) and
                                  (CurrentChar <= ord('F')))) and not CharEOF
                                then
                                begin
                                  if (CurrentChar >= ord('0')) and
                                    (CurrentChar <= ord('9')) then
                                  begin
                                    wc := wc or
                                      UInt16(UInt16(CurrentChar) - ord('0'));
                                  end
                                  else if (CurrentChar >= ord('a')) and
                                    (CurrentChar <= ord('f')) then
                                  begin
                                    wc := wc or
                                      UInt16((UInt16(CurrentChar) -
                                      ord('a')) + $A);
                                  end
                                  else if (CurrentChar >= ord('A')) and
                                    (CurrentChar <= ord('F')) then
                                  begin
                                    wc := wc or
                                      UInt16((UInt16(CurrentChar) -
                                      ord('A')) + $A);
                                  end;
                                  AddChar(wc);
                                  NextChar;
                                end
                                else
                                begin
                                  JSONError;
                                end;
                              end
                              else
                              begin
                                JSONError;
                              end;
                            end
                            else
                            begin
                              JSONError;
                            end;
                          end
                          else
                          begin
                            JSONError;
                          end;
                        end;
                    else
                      begin
                        JSONError;
                      end;
                    end;
                  end;
              else
                begin
                  AddChar(CurrentChar);
                  NextChar;
                end;
              end;
            end;
            ExpectChar('"');
            SetLength(w, wl);
            result := TPasJSONItemString.Create(ConvertUTF16ToUTF8(w));
          end
          else
          begin
            JSONError;
          end;
          SkipWhite;
        except
          FreeAndNil(result);
          raise;
        end;
      finally
        w := '';
      end;
    end;
    function ParseNumber: TPasJSONItem;
    var
      s: TPasJSONRawByteString;
      OK: TPasDblStrUtilsBoolean;
      Value: TPasJSONDouble;
      IsHexadecimal: boolean;
    begin
      result := nil;
      s := '';
      try
        try
          if CharEOF then
          begin
            JSONError;
          end;
          case CurrentChar of
            ord('-'), ord('0') .. ord('9'):
              begin
                if IsChar('-') then
                begin
                  s := s + AnsiChar(byte(CurrentChar));
                  NextChar;
                  if CharEOF or not((CurrentChar >= ord('0')) and
                    (CurrentChar <= ord('9'))) then
                  begin
                    JSONError;
                  end;
                end;
                IsHexadecimal := false;
                if (not CharEOF) and (CurrentChar = ord('0')) then
                begin
                  s := s + AnsiChar(byte(CurrentChar));
                  NextChar;
                  if (TPasJSONModeFlag.HexadecimalNumbers in aModeFlags) and
                    ((not CharEOF) and (CurrentChar in [ord('x'), ord('X')]))
                  then
                  begin
                    s := s + AnsiChar(byte(CurrentChar));
                    NextChar;
                    while (not CharEOF) and
                      (((CurrentChar >= ord('0')) and (CurrentChar <= ord('9')))
                      or ((CurrentChar >= ord('a')) and (CurrentChar <= ord('f')
                      )) or ((CurrentChar >= ord('A')) and
                      (CurrentChar <= ord('F')))) do
                    begin
                      s := s + AnsiChar(byte(CurrentChar));
                      NextChar;
                    end;
                    IsHexadecimal := true;
                  end
                  else
                  begin
                    if (not CharEOF) and
                      ((CurrentChar >= ord('0')) and (CurrentChar <= ord('9')))
                    then
                    begin
                      JSONError;
                    end;
                  end;
                end
                else
                begin
                  while (not CharEOF) and
                    ((CurrentChar >= ord('0')) and (CurrentChar <= ord('9'))) do
                  begin
                    s := s + AnsiChar(byte(CurrentChar));
                    NextChar;
                  end;
                end;
                if IsChar('.') then
                begin
                  s := s + AnsiChar(byte(CurrentChar));
                  NextChar;
                  if CharEOF or
                    not(((CurrentChar >= ord('0')) and (CurrentChar <= ord('9'))
                    ) or (IsHexadecimal and (((CurrentChar >= ord('a')) and
                    (CurrentChar <= ord('f'))) or ((CurrentChar >= ord('A')) and
                    (CurrentChar <= ord('F')))))) then
                  begin
                    JSONError;
                  end;
                  while (not CharEOF) and
                    (((CurrentChar >= ord('0')) and (CurrentChar <= ord('9')))
                    or (IsHexadecimal and (((CurrentChar >= ord('a')) and
                    (CurrentChar <= ord('f'))) or ((CurrentChar >= ord('A')) and
                    (CurrentChar <= ord('F')))))) do
                  begin
                    s := s + AnsiChar(byte(CurrentChar));
                    NextChar;
                  end;
                end;
                if (not CharEOF) and
                  ((CurrentChar = ord('e')) or (CurrentChar = ord('E'))) then
                begin
                  s := s + AnsiChar(byte(CurrentChar));
                  NextChar;
                  if (not CharEOF) and
                    ((CurrentChar = ord('-')) or (CurrentChar = ord('+'))) then
                  begin
                    s := s + AnsiChar(byte(CurrentChar));
                    NextChar;
                  end;
                  if CharEOF or not((CurrentChar >= ord('0')) and
                    (CurrentChar <= ord('9'))) then
                  begin
                    JSONError;
                  end;
                  while (not CharEOF) and
                    ((CurrentChar >= ord('0')) and (CurrentChar <= ord('9'))) do
                  begin
                    s := s + AnsiChar(byte(CurrentChar));
                    NextChar;
                  end;
                end;
              end
          else
            begin
              JSONError;
            end;
          end;
          if length(s) > 0 then
          begin
            OK := false;
            Value := ConvertStringToDouble(s, rmNearest, @OK);
            if not OK then
            begin
              JSONError;
            end;
            result := TPasJSONItemNumber.Create(Value);
          end;
        except
          FreeAndNil(result);
          raise;
        end;
      finally
        s := '';
      end;
    end;
    procedure ParseObjectProperty(DestinationObject: TPasJSONItemObject);
    var
      Key: TPasJSONUTF8String;
      Value: TPasJSONItem;
    begin
      result := nil;
      try
        if (TPasJSONModeFlag.UnquotedKeys in aModeFlags) and (not CharEOF) and
          ((CurrentChar = UInt16(TPasJSONUTF16Char('_'))) or
          ((CurrentChar >= UInt16(TPasJSONUTF16Char('a'))) and
          (CurrentChar <= UInt16(TPasJSONUTF16Char('z')))) or
          ((CurrentChar >= UInt16(TPasJSONUTF16Char('A'))) and
          (CurrentChar <= UInt16(TPasJSONUTF16Char('Z')))) or
          ((CurrentChar >= UInt16(TPasJSONUTF16Char('0'))) and
          (CurrentChar <= UInt16(TPasJSONUTF16Char('9'))))) then
        begin
          Key := TPasJSONUTF8String(AnsiChar(byte(CurrentChar)));
          NextChar;
          while ((CurrentChar = UInt16(TPasJSONUTF16Char('_'))) or
            ((CurrentChar >= UInt16(TPasJSONUTF16Char('a'))) and
            (CurrentChar <= UInt16(TPasJSONUTF16Char('z')))) or
            ((CurrentChar >= UInt16(TPasJSONUTF16Char('A'))) and
            (CurrentChar <= UInt16(TPasJSONUTF16Char('Z')))) or
            ((CurrentChar >= UInt16(TPasJSONUTF16Char('0'))) and
            (CurrentChar <= UInt16(TPasJSONUTF16Char('9'))))) do
          begin
            Key := Key + TPasJSONUTF8String(AnsiChar(byte(CurrentChar)));
            NextChar;
          end;
          SkipWhite;
        end
        else
        begin
          Value := ParseString;
          if assigned(Value) and (Value is TPasJSONItemString) then
          begin
            Key := TPasJSONItemString(Value).Value;
            FreeAndNil(Value);
          end
          else
          begin
            FreeAndNil(Value);
            JSONError;
            Key := '';
          end;
        end;
        SkipWhite;
        if TPasJSONModeFlag.EqualsForColon in aModeFlags then
        begin
          ExpectChar('=');
        end
        else
        begin
          ExpectChar(':');
        end;
        SkipWhite;
        Value := ParseValue(false);
        DestinationObject.Add(Key, Value);
      except
        FreeAndNil(result);
        raise;
      end;
    end;
    function ParseObject(const ImplicitRootObject: boolean): TPasJSONItem;
    begin
      result := TPasJSONItemObject.Create;
      try
        if not ImplicitRootObject then
        begin
          ExpectChar('{');
        end;
        SkipWhite;
        while not(CharEOF or ((not ImplicitRootObject) and IsChar('}'))) do
        begin
          ParseObjectProperty(TPasJSONItemObject(result));
          SkipWhite;
          if IsChar(',') then
          begin
            NextChar;
            SkipWhite;
            if (not(CharEOF or ImplicitRootObject)) and IsChar('}') then
            begin
              JSONError;
            end;
          end
          else if not(TPasJSONModeFlag.OptionalCommas in aModeFlags) then
          begin
            break;
          end;
        end;
        if not ImplicitRootObject then
        begin
          ExpectChar('}');
        end;
      except
        FreeAndNil(result);
        raise;
      end;
    end;
    function ParseArray: TPasJSONItem;
    begin
      result := TPasJSONItemArray.Create;
      try
        ExpectChar('[');
        SkipWhite;
        while not(CharEOF or IsChar(']')) do
        begin
          TPasJSONItemArray(result).Add(ParseValue(false));
          SkipWhite;
          if IsChar(',') then
          begin
            NextChar;
            SkipWhite;
            if (not CharEOF) and IsChar(']') then
            begin
              JSONError;
            end;
          end
          else if not(TPasJSONModeFlag.OptionalCommas in aModeFlags) then
          begin
            break;
          end;
        end;
        ExpectChar(']');
      except
        FreeAndNil(result);
        raise;
      end;
    end;
    procedure ExpectKeyword(const Keyword: TPasJSONUTF8String);
    var
      i: TPasJSONInt32;
    begin
      for i := 1 to length(Keyword) do
      begin
        ExpectChar(TPasJSONUTF16Char(Keyword[i]));
      end;
    end;

  begin
    result := nil;
    try
      SkipWhite;
      if CharEOF then
      begin
        JSONError;
      end;
      if ImplicitRootObject then
      begin
        result := ParseObject(true);
      end
      else
      begin
        case CurrentChar of
          ord('{'):
            begin
              result := ParseObject(false);
            end;
          ord('['):
            begin
              result := ParseArray;
            end;
          ord('"'):
            begin
              result := ParseString;
            end;
          ord('-'), ord('0') .. ord('9'):
            begin
              result := ParseNumber;
            end;
          ord('t'):
            begin
              ExpectKeyword('true');
              result := TPasJSONItemBoolean.Create(true);
            end;
          ord('f'):
            begin
              ExpectKeyword('false');
              result := TPasJSONItemBoolean.Create(false);
            end;
          ord('n'):
            begin
              ExpectKeyword('null');
              result := TPasJSONItemNull.Create;
            end;
        else
          begin
            JSONError;
          end;
        end;
      end;
    except
      FreeAndNil(result);
      raise;
    end;
  end;

begin
  result := nil;
  try
    CharEOF := false;
    if aEncoding = TPasJSONEncoding.AutomaticDetection then
    begin
      if (length(aSource) >= 3) and ((byte(AnsiChar(aSource[1])) = $EF) and
        (byte(AnsiChar(aSource[2])) = $BB) and
        (byte(AnsiChar(aSource[3])) = $BF)) then
      begin
        Position := 4;
        Encoding := TPasJSONEncoding.UTF8;
      end
      else if (length(aSource) >= 4) and ((byte(AnsiChar(aSource[1])) = $FF) and
        (byte(AnsiChar(aSource[2])) = $FE) and
        (byte(AnsiChar(aSource[3])) = $00) and
        (byte(AnsiChar(aSource[4])) = $00)) then
      begin
        Position := 5;
        Encoding := TPasJSONEncoding.UTF32LE;
      end
      else if (length(aSource) >= 4) and ((byte(AnsiChar(aSource[1])) = $00) and
        (byte(AnsiChar(aSource[2])) = $00) and
        (byte(AnsiChar(aSource[3])) = $FE) and
        (byte(AnsiChar(aSource[4])) = $FF)) then
      begin
        Position := 5;
        Encoding := TPasJSONEncoding.UTF32BE;
      end
      else if (length(aSource) >= 2) and ((byte(AnsiChar(aSource[1])) = $FF) and
        (byte(AnsiChar(aSource[2])) = $FE)) then
      begin
        Position := 3;
        Encoding := TPasJSONEncoding.UTF16LE;
      end
      else if (length(aSource) >= 2) and ((byte(AnsiChar(aSource[1])) = $FE) and
        (byte(AnsiChar(aSource[2])) = $FF)) then
      begin
        Position := 3;
        Encoding := TPasJSONEncoding.UTF16BE;
      end
      else
      begin
        Position := 1;
        Encoding := TPasJSONEncoding.Latin1;
      end;
    end
    else
    begin
      Position := 1;
      Encoding := aEncoding;
    end;
    NextChar;
    result := ParseValue(TPasJSONModeFlag.ImplicitRootObject in aModeFlags);
    SkipWhite;
    if not CharEOF then
    begin
      JSONError;
    end;
  except
    FreeAndNil(result);
    raise;
  end;
end;

class function TPasJSON.Parse(const aStream: TStream;
  const aModeFlags: TPasJSONModeFlags = [TPasJSONModeFlag.Comments];
  const aEncoding: TPasJSONEncoding = TPasJSONEncoding.AutomaticDetection)
  : TPasJSONItem;
var
  StringValue: TPasJSONRawByteString;
begin
  StringValue := '';
  try
    SetLength(StringValue, aStream.Size);
    if length(StringValue) > 0 then
    begin
      if aStream.Seek(0, soBeginning) <> 0 then
      begin
        raise EInOutError.Create('Stream seek error');
      end;
      aStream.ReadBuffer(StringValue[1], aStream.Size);
    end;
    result := TPasJSON.Parse(StringValue, aModeFlags, aEncoding);
  finally
    StringValue := '';
  end;
end;

class function TPasJSON.Stringify(const aJSONItem: TPasJSONItem;
  const aFormatting: boolean = false; const aModeFlags: TPasJSONModeFlags = [];
  const aLevel: TPasJSONInt32 = 0): TPasJSONRawByteString;
  function IsIdentifier(const pKey: TPasJSONUTF8String): boolean;
  var
    Index: TPasJSONInt32;
  begin
    result := length(pKey) > 0;
    for Index := 1 to length(pKey) do
    begin
      case UInt16(TPasJSONUTF16Char(pKey[Index])) of
        UInt16(TPasJSONUTF16Char('_')), UInt16(TPasJSONUTF16Char('a'))
          .. UInt16(TPasJSONUTF16Char('z')), UInt16(TPasJSONUTF16Char('A'))
          .. UInt16(TPasJSONUTF16Char('Z')), UInt16(TPasJSONUTF16Char('0'))
          .. UInt16(TPasJSONUTF16Char('9')):
          begin
          end;
      else
        begin
          result := false;
          break;
        end;
      end;
    end;
  end;

var
  Index, Count, Ident, LevelOffset: TPasJSONInt32;
  IntegerValue: Int64;
  Key: TPasJSONUTF8String;
begin
  if assigned(aJSONItem) then
  begin
    if aJSONItem is TPasJSONItemNull then
    begin
      result := 'null';
    end
    else if aJSONItem is TPasJSONItemBoolean then
    begin
      if TPasJSONItemBoolean(aJSONItem).Value then
      begin
        result := 'true';
      end
      else
      begin
        result := 'false';
      end;
    end
    else if aJSONItem is TPasJSONItemNumber then
    begin
      if IsInfinite(TPasJSONItemNumber(aJSONItem).Value) then
      begin
        if (TPasJSONUInt64(Pointer(@TPasJSONItemNumber(aJSONItem).Value)^)
          shr 63) <> 0 then
        begin
          result := TPasJSON.StringQuote('-Infinity');
        end
        else
        begin
          result := TPasJSON.StringQuote('Infinity');
        end;
      end
      else if IsNaN(TPasJSONItemNumber(aJSONItem).Value) then
      begin
        if (TPasJSONUInt64(Pointer(@TPasJSONItemNumber(aJSONItem).Value)^)
          shr 63) <> 0 then
        begin
          result := TPasJSON.StringQuote('-NaN');
        end
        else
        begin
          result := TPasJSON.StringQuote('NaN');
        end;
      end
      else
      begin
        IntegerValue := trunc(TPasJSONItemNumber(aJSONItem).Value);
        if TPasJSONItemNumber(aJSONItem).Value = IntegerValue then
        begin
          Str(IntegerValue, result);
        end
        else
        begin
          result := ConvertDoubleToString(TPasJSONItemNumber(aJSONItem).Value,
            omStandard, 0);
        end;
      end;
    end
    else if aJSONItem is TPasJSONItemString then
    begin
      result := TPasJSON.StringQuote(TPasJSONItemString(aJSONItem).Value);
    end
    else if aJSONItem is TPasJSONItemArray then
    begin
      result := '[';
      if aFormatting then
      begin
        result := result + #13#10;
      end;
      Count := TPasJSONItemArray(aJSONItem).Count;
      if TPasJSONModeFlag.ImplicitRootObject in aModeFlags then
      begin
        LevelOffset := -1;
      end
      else
      begin
        LevelOffset := 0;
      end;
      for Index := 0 to Count - 1 do
      begin
        if aFormatting then
        begin
          for Ident := 0 to aLevel + LevelOffset do
          begin
            result := result + '  ';
          end;
        end;
        result := result + TPasJSON.Stringify(TPasJSONItemArray(aJSONItem)
          .Items[Index], aFormatting, aModeFlags, aLevel + 1);
        if ((Index + 1) < Count) and not(TPasJSONModeFlag.OptionalCommas
          in aModeFlags) then
        begin
          result := result + ',';
        end;
        if aFormatting then
        begin
          result := result + #13#10;
        end;
      end;
      if aFormatting then
      begin
        for Ident := 1 to aLevel + LevelOffset do
        begin
          result := result + '  ';
        end;
      end;
      result := result + ']';
    end
    else if aJSONItem is TPasJSONItemObject then
    begin
      if (not(TPasJSONModeFlag.ImplicitRootObject in aModeFlags)) or
        (aLevel <> 0) then
      begin
        result := '{';
        if aFormatting then
        begin
          result := result + #13#10;
        end;
      end
      else
      begin
        result := '';
      end;
      if TPasJSONModeFlag.ImplicitRootObject in aModeFlags then
      begin
        LevelOffset := -1;
      end
      else
      begin
        LevelOffset := 0;
      end;
      Count := TPasJSONItemArray(aJSONItem).Count;
      for Index := 0 to Count - 1 do
      begin
        if aFormatting then
        begin
          for Ident := 0 to aLevel + LevelOffset do
          begin
            result := result + '  ';
          end;
        end;
        Key := TPasJSONItemObject(aJSONItem).Keys[Index];
        if (TPasJSONModeFlag.UnquotedKeys in aModeFlags) and IsIdentifier(Key)
        then
        begin
          result := result + TPasJSONRawByteString(Key);
        end
        else
        begin
          result := result + TPasJSON.StringQuote(Key);
        end;
        if TPasJSONModeFlag.EqualsForColon in aModeFlags then
        begin
          if aFormatting then
          begin
            result := result + ' ';
          end;
          result := result + '=';
        end
        else
        begin
          result := result + ':';
        end;
        if aFormatting then
        begin
          result := result + ' ';
        end;
        result := result + TPasJSON.Stringify(TPasJSONItemObject(aJSONItem)
          .Values[Index], aFormatting, aModeFlags, aLevel + 1);
        if ((Index + 1) < Count) and not(TPasJSONModeFlag.OptionalCommas
          in aModeFlags) then
        begin
          result := result + ',';
        end;
        if aFormatting then
        begin
          result := result + #13#10;
        end;
      end;
      if aFormatting then
      begin
        for Ident := 1 to aLevel + LevelOffset do
        begin
          result := result + '  ';
        end;
      end;
      if (not(TPasJSONModeFlag.ImplicitRootObject in aModeFlags)) or
        (aLevel <> 0) then
      begin
        result := result + '}';
      end;
    end
    else
    begin
      result := 'null';
    end;
  end
  else
  begin
    result := 'null';
  end;
end;

class procedure TPasJSON.StringifyToStream(const aStream: TStream;
  const aJSONItem: TPasJSONItem; const aFormatting: boolean = false;
  const aModeFlags: TPasJSONModeFlags = []; const aLevel: TPasJSONInt32 = 0);
var
  StringValue: TPasJSONRawByteString;
begin
  StringValue := Stringify(aJSONItem, aFormatting, aModeFlags, aLevel);
  try
    if length(StringValue) > 0 then
    begin
      aStream.WriteBuffer(StringValue[1], length(StringValue));
    end;
  finally
    StringValue := '';
  end;
end;

class function TPasJSON.GetNumber(const aItem: TPasJSONItem;
  const aDefault: TPasJSONDouble = 0.0): TPasJSONDouble;
var
  OK: TPasDblStrUtilsBoolean;
begin
  if assigned(aItem) and (aItem is TPasJSONItemNumber) then
  begin
    result := TPasJSONItemNumber(aItem).Value;
  end
  else if assigned(aItem) and (aItem is TPasJSONItemString) then
  begin
    OK := false;
    result := ConvertStringToDouble(TPasJSONItemString(aItem).Value,
      rmNearest, @OK, -1);
    if not OK then
    begin
      result := aDefault;
    end;
  end
  else if assigned(aItem) and (aItem is TPasJSONItemBoolean) then
  begin
    result := ord(TPasJSONItemBoolean(aItem).Value) and 1;
  end
  else
  begin
    result := aDefault;
  end;
end;

class function TPasJSON.GetInt64(const aItem: TPasJSONItem;
  const aDefault: TPasJSONInt64 = 0): TPasJSONInt64;
begin
  if assigned(aItem) and (aItem is TPasJSONItemNumber) then
  begin
    result := trunc(TPasJSONItemNumber(aItem).Value);
  end
  else if assigned(aItem) and (aItem is TPasJSONItemString) then
  begin
    result := StrToInt64Def(TPasJSONItemString(aItem).Value, aDefault);
  end
  else if assigned(aItem) and (aItem is TPasJSONItemBoolean) then
  begin
    result := ord(TPasJSONItemBoolean(aItem).Value) and 1;
  end
  else
  begin
    result := aDefault;
  end;
end;

class function TPasJSON.GetString(const aItem: TPasJSONItem;
  const aDefault: TPasJSONUTF8String = ''): TPasJSONUTF8String;
begin
  if assigned(aItem) and (aItem is TPasJSONItemString) then
  begin
    result := TPasJSONItemString(aItem).Value;
  end
  else if assigned(aItem) and (aItem is TPasJSONItemNumber) then
  begin
    result := ConvertDoubleToString(TPasJSONItemNumber(aItem).Value,
      omStandard);
  end
  else if assigned(aItem) and (aItem is TPasJSONItemBoolean) then
  begin
    if TPasJSONItemBoolean(aItem).Value then
    begin
      result := 'true';
    end
    else
    begin
      result := 'false';
    end;
  end
  else
  begin
    result := aDefault;
  end;
end;

class function TPasJSON.GetBoolean(const aItem: TPasJSONItem;
  const aDefault: boolean = false): boolean;
begin
  if assigned(aItem) and (aItem is TPasJSONItemBoolean) then
  begin
    result := TPasJSONItemBoolean(aItem).Value;
  end
  else if assigned(aItem) and (aItem is TPasJSONItemNumber) then
  begin
    result := TPasJSONItemNumber(aItem).Value <> 0.0;
  end
  else if assigned(aItem) and (aItem is TPasJSONItemString) then
  begin
    result := (LowerCase(TPasJSONItemString(aItem).Value) <> 'false') and
      (TPasJSONItemString(aItem).Value <> '0');
  end
  else
  begin
    result := aDefault;
  end;
end;

class function TPasJSON.LoadBinaryFromStream(const aStream: TStream)
  : TPasJSONItem;
  function LoadJSONItem: TPasJSONItem;
  var
    ItemType, BooleanValue: TPasJSONUInt8;
    Count, Counter, Len: TPasJSONInt32;
    TempString: TPasJSONUTF8String;
    DoubleValue: TPasJSONDouble;
  begin
    result := nil;
    if aStream.Read(ItemType, SizeOf(TPasJSONUInt8)) <> SizeOf(TPasJSONUInt8)
    then
    begin
      raise EInOutError.Create('Stream read error');
    end;
    case ItemType of
      0:
        begin
          result := TPasJSONItemNull.Create;
        end;
      1:
        begin
          result := TPasJSONItemArray.Create;
          if aStream.Read(Count, SizeOf(TPasJSONUInt32)) <> SizeOf(TPasJSONInt32)
          then
          begin
            raise EInOutError.Create('Stream read error');
          end;
          for Counter := 0 to Count - 1 do
          begin
            TPasJSONItemArray(result).Add(LoadJSONItem);
          end;
        end;
      2:
        begin
          TempString := '';
          try
            result := TPasJSONItemObject.Create;
            if aStream.Read(Count, SizeOf(TPasJSONUInt32)) <>
              SizeOf(TPasJSONInt32) then
            begin
              raise EInOutError.Create('Stream read error');
            end;
            for Counter := 0 to Count - 1 do
            begin
              if aStream.Read(Len, SizeOf(TPasJSONUInt32)) <>
                SizeOf(TPasJSONInt32) then
              begin
                raise EInOutError.Create('Stream read error');
              end;
              SetLength(TempString, Len);
              if Len > 0 then
              begin
                if TPasJSONInt64(aStream.Read(TempString[1],
                  Len * SizeOf(AnsiChar))) <>
                  TPasJSONInt64(Len * SizeOf(AnsiChar)) then
                begin
                  raise EInOutError.Create('Stream read error');
                end;
              end;
              TPasJSONItemObject(result).Add(TempString, LoadJSONItem);
            end;
          finally
            TempString := '';
          end;
        end;
      3:
        begin
          if aStream.Read(BooleanValue, SizeOf(TPasJSONUInt8)) <>
            SizeOf(TPasJSONUInt8) then
          begin
            raise EInOutError.Create('Stream read error');
          end;
          result := TPasJSONItemBoolean.Create(BooleanValue <> 0);
        end;
      4:
        begin
          if aStream.Read(DoubleValue, SizeOf(TPasJSONDouble)) <>
            SizeOf(TPasJSONDouble) then
          begin
            raise EInOutError.Create('Stream read error');
          end;
          result := TPasJSONItemNumber.Create(DoubleValue);
        end;
      5:
        begin
          TempString := '';
          try
            if aStream.Read(Len, SizeOf(TPasJSONInt32)) <> SizeOf(TPasJSONInt32)
            then
            begin
              raise EInOutError.Create('Stream read error');
            end;
            SetLength(TempString, Len);
            if Len > 0 then
            begin
              if TPasJSONInt64(aStream.Read(TempString[1],
                Len * SizeOf(AnsiChar))) <> TPasJSONInt64(Len * SizeOf(AnsiChar))
              then
              begin
                raise EInOutError.Create('Stream read error');
              end;
            end;
            result := TPasJSONItemString.Create(TempString);
          finally
            TempString := '';
          end;
        end;
    else
      begin
        raise EInOutError.Create('Stream read error');
      end;
    end;
  end;

begin
  if assigned(aStream) and (aStream.Position < aStream.Size) then
  begin
    result := LoadJSONItem;
  end
  else
  begin
    result := nil;
  end;
end;

class procedure TPasJSON.SaveBinaryToStream(const aStream: TStream;
  const aJSONItem: TPasJSONItem);
  procedure SaveJSONItem(const pCurrentJSONItem: TPasJSONItem);
  var
    ItemType, BooleanValue: TPasJSONUInt8;
    Count, Counter, Len: TPasJSONInt32;
    TempString: TPasJSONUTF8String;
    DoubleValue: TPasJSONDouble;
  begin
    if assigned(pCurrentJSONItem) then
    begin
      if pCurrentJSONItem is TPasJSONItemNull then
      begin
        ItemType := 0;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
      end
      else if pCurrentJSONItem is TPasJSONItemArray then
      begin
        ItemType := 1;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        Count := TPasJSONItemArray(pCurrentJSONItem).Count;
        if aStream.Write(Count, SizeOf(TPasJSONInt32)) <> SizeOf(TPasJSONInt32)
        then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        for Counter := 0 to Count - 1 do
        begin
          SaveJSONItem(TPasJSONItemArray(pCurrentJSONItem).Items[Counter]);
        end;
      end
      else if pCurrentJSONItem is TPasJSONItemObject then
      begin
        ItemType := 2;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        Count := TPasJSONItemObject(pCurrentJSONItem).Count;
        if aStream.Write(Count, SizeOf(TPasJSONInt32)) <> SizeOf(TPasJSONInt32)
        then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        try
          for Counter := 0 to Count - 1 do
          begin
            TempString := TPasJSONItemObject(pCurrentJSONItem).Keys[Counter];
            Len := length(TempString);
            if aStream.Write(Len, SizeOf(TPasJSONInt32)) <> SizeOf(TPasJSONInt32)
            then
            begin
              raise EInOutError.Create('Stream write error');
            end;
            if Len > 0 then
            begin
              if TPasJSONInt64(aStream.Write(TempString[1],
                Len * SizeOf(AnsiChar))) <> TPasJSONInt64(Len * SizeOf(AnsiChar))
              then
              begin
                raise EInOutError.Create('Stream write error');
              end;
            end;
            SaveJSONItem(TPasJSONItemObject(pCurrentJSONItem).Values[Counter]);
          end;
        finally
          TempString := '';
        end;
      end
      else if pCurrentJSONItem is TPasJSONItemBoolean then
      begin
        ItemType := 3;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        if TPasJSONItemBoolean(pCurrentJSONItem).fValue then
        begin
          BooleanValue := $FF;
        end
        else
        begin
          BooleanValue := $00;
        end;
        if aStream.Write(BooleanValue, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
      end
      else if pCurrentJSONItem is TPasJSONItemNumber then
      begin
        ItemType := 4;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        DoubleValue := TPasJSONItemNumber(pCurrentJSONItem).fValue;
        if aStream.Write(DoubleValue, SizeOf(TPasJSONDouble)) <>
          SizeOf(TPasJSONDouble) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
      end
      else if pCurrentJSONItem is TPasJSONItemString then
      begin
        ItemType := 5;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        TempString := TPasJSONItemString(pCurrentJSONItem).fValue;
        Len := length(TempString);
        if aStream.Write(Len, SizeOf(TPasJSONInt32)) <> SizeOf(TPasJSONInt32)
        then
        begin
          raise EInOutError.Create('Stream write error');
        end;
        if Len > 0 then
        begin
          if TPasJSONInt64(aStream.Write(TempString[1], Len * SizeOf(AnsiChar)))
            <> TPasJSONInt64(Len * SizeOf(AnsiChar)) then
          begin
            raise EInOutError.Create('Stream write error');
          end;
        end;
      end
      else
      begin
        ItemType := 0;
        if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <>
          SizeOf(TPasJSONUInt8) then
        begin
          raise EInOutError.Create('Stream write error');
        end;
      end;
    end
    else
    begin
      ItemType := 0;
      if aStream.Write(ItemType, SizeOf(TPasJSONUInt8)) <> SizeOf(TPasJSONUInt8)
      then
      begin
        raise EInOutError.Create('Stream write error');
      end;
    end;
  end;

begin
  if assigned(aJSONItem) and assigned(aStream) then
  begin
    SaveJSONItem(aJSONItem);
  end;
end;

initialization

end.
